﻿//----------------------------------------------------------------
// Copyright (c) 2011-2013 
// All rights reserved.
// Author: Ben DaoWei 
// Contact: bendaowei@gmail.com
// Web Link: http://xnaeditor.codeplex.com
// ----------------------------------------------------------------

namespace UnityEngine.Components
{
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using UnityEngine.Core;
    using System.Xml;
    using UnityEngine.IO;
    using System.Xml.Serialization;
    using Microsoft.Xna.Framework.Input;
    using System;
    using UnityEngine.Graphics;
    using UnityEngine.Gameplay;
    using UnityEngine.Terrain;
    using UnityEngine.Primitive;
    using System.Diagnostics;
    using UnityEngine.Gameplay.UI;
    using System.Collections.Generic;
    using UnityEngine.Gameplay.Player;
    using UnityEngine.Gameplay.Match;
    using UnityEngine.Audio;

    public class GameCamera : Camera
    {
        public override int Priority
        {
            get
            {
                return 2000;
            }
        }

        public Vector2 BoundaryMin
        {
            get;
            set;
        }

        public Vector2 BoundaryMax
        {
            get;
            set;
        }

        private Rectangle _SelectionRect;
        public Rectangle SelectionRect
        {
            get
            {
                return _SelectionRect;
            }
            set
            {
                _SelectionRect = value;
            }
        }

        [Editable]
        public Vector2 DistanceRange
        {
            get;
            set;
        }

        [Editable]
        public float Distance
        {
            get;
            set;
        }

        [Editable]
        public float ZoomSpeed
        {
            get;
            set;
        }

        [Editable]
        public float Margin
        {
            get;
            set;
        }

        [Editable]
        public float RotateSpeed
        {
            get;
            set;
        }

        private float _MoveSpeed;
        [Editable]
        public float MoveSpeed
        {
            get
            {
                return _MoveSpeed;
            }
            set
            {
                _MoveSpeed = value;
            }
        }

        private Vector3 _Rotation = Vector3.Zero;
        [Editable]
        public Vector3 Rotation
        {
            get
            {
                return _Rotation;
            }
            set
            {
                _Rotation = value;
            }
        }

        public GameCamera()
            : base(null)
        {
            this.Reset();
        }

        public GameCamera(GameObject gameObject)
            : base(gameObject)
        {
            this.Reset();
        }

        public void Reset()
        {
            this.RotateSpeed = 1.0f;
            this.MoveSpeed = 500.0f;
            this.ZoomSpeed = 100.0f;
            this.Distance = 3000;
            this.DistanceRange = new Vector2(1500, 4000);
            this.Rotation = new Vector3(0, 0, (float)Math.PI / 4);
            this.Margin = 20;

            float margin = 6 * 128;
            float minX = 0, minZ = 0, maxX = 128 * 128, maxZ = 128 * 128;
            this.BoundaryMin = new Vector2(minX + margin, minZ + margin);
            this.BoundaryMax = new Vector2(maxX - margin, maxZ - margin);

            this.SelectionRect = Rectangle.Empty;
        }

        public void Translate(Vector3 offset)
        {
            this.Position += offset;
            this.LookAt += offset;
        }

        public void CalculatePosition()
        {
            // Clap the range
            if (Distance < DistanceRange.X)
                Distance = DistanceRange.X;
            if (Distance > DistanceRange.Y)
                Distance = DistanceRange.Y;

            // calculate the positions
            Matrix rotation = Matrix.CreateFromYawPitchRoll(this.Rotation.X, this.Rotation.Y, this.Rotation.Z);
            this.Position = Vector3.Transform(new Vector3(0, Distance, 0), rotation) + this.LookAt;
            this.Up = Vector3.Transform(new Vector3(1, 0, 0), rotation);

            // Clap the camera position
            Vector3 offset = Vector3.Zero;
            if (this.LookAt.X < this.BoundaryMin.X) offset.X = this.BoundaryMin.X - this.LookAt.X;
            if (this.LookAt.Z < this.BoundaryMin.Y) offset.Z = this.BoundaryMin.Y - this.LookAt.Z;

            if (this.BoundaryMax.X < this.LookAt.X) offset.X = this.BoundaryMax.X - this.LookAt.X;
            if (this.BoundaryMax.Y < this.LookAt.Z) offset.Z = this.BoundaryMax.Y - this.LookAt.Z;
            this.Translate(offset);
        }

        private Vector3 GetTerrainPosition()
        {
            Landscape landscape = this.GameObject.GameScene.GetComponentInChildren<Landscape>();

            InputManager input = this.GameObject.GameScene.Game.InputManager;
            Ray ray = this.GetRayFromScreen(input.MouseX, input.MouseY);

            Vector3 result = Vector3.Zero;

            float left = -10000;
            float right = 10000;
            float mid = (left + right) / 2.0f;
            while ((right - left) > 0.1f)
            {
                mid = (left + right) / 2.0f;
                Vector3 p = ray.Position + mid * ray.Direction;
                Vector3 shouldP;
                landscape.WarCraftMapPath.GetHeight(p, out shouldP);

                if (shouldP.Y < p.Y)
                    left = mid;
                else
                    right = mid;

                result = shouldP;
            }

            return result;
        }

        private void UpdateClicks()
        {
            InputManager input = this.GameObject.GameScene.Game.InputManager;

            bool leftButtonJustPressed = false;
            bool rightButtonJustPressed = false;
            if (input.CurrentMouseState.LeftButton == ButtonState.Pressed && input.LastMouseState.LeftButton == ButtonState.Released)
                leftButtonJustPressed = true;
            if (input.CurrentMouseState.RightButton == ButtonState.Pressed && input.LastMouseState.RightButton == ButtonState.Released)
                rightButtonJustPressed = true;

            Vector3 terrainPosition = GetTerrainPosition();
            SelectableObject selectedObject = null;
            bool clickInRange = false;
            if ((leftButtonJustPressed || rightButtonJustPressed) && (input.MouseY < input.newY(520)))
            {
                clickInRange = true;

                SelectableObject[] objects = this.GameObject.GameScene.GetComponentsInChildren<SelectableObject>();

                Ray ray = this.GetRayFromScreen(input.MouseY, input.MouseY);

                SelectableObject targetForUI = null;

                for (int i = 0; i < objects.Length; i++)
                {
                    SelectableObject target = objects[i];

                    // method 1
                    var surface = target.GameObject.GetComponent<OnSurfaceObject>();
                    if (surface.ContainPosition(terrainPosition))
                        targetForUI = target;

                    // method 2
                    //var surface = target.GameObject.GetComponent<OnSurfaceObject>();
                    //Vector3[] points = surface.CollisionCorners;
                    //List<Vector3> pList = new List<Vector3>();
                    //float addH = target.GameObject.GetComponent<UnitObject>().ObjectSize.Y;
                    //for (int j = 0; j < points.Length; j++)
                    //{
                    //    pList.Add(points[j]);
                    //    pList.Add(new Vector3(points[j].X, points[j].Y + addH, points[j].Z));
                    //}
                    //BoundingBox box = BoundingBox.CreateFromPoints(pList);

                    //if (box.Intersects(ray) != null)
                    //    targetForUI = target;

                    // method 3
                    //Renderer r = target.GameObject.GetComponent<Renderer>();
                    //if (r.ModelRenderer.Intersects(ray, r.Instance.World))
                    //    targetForUI = target;
                }

                selectedObject = targetForUI;

            }

            ControlPanel panel = this.GameObject.GameScene.GetComponentInChildren<ControlPanel>();

            switch (panel.ControlPanelState)
            {
                case ControlPanelState.MoveWaitForTarget:
                    if (leftButtonJustPressed && clickInRange)
                    {
                        SendCommand(CommandType.Move, selectedObject, terrainPosition);
                        panel.ControlPanelState = ControlPanelState.None;
                    }
                    break;
                case ControlPanelState.PatrolWaitForTarget:
                    if (leftButtonJustPressed && clickInRange)
                    {
                        SendCommand(CommandType.Patrol, selectedObject, terrainPosition);
                        panel.ControlPanelState = ControlPanelState.None;
                    }
                    break;
                case ControlPanelState.AttackWaitForTarget:
                    if (leftButtonJustPressed && clickInRange)
                    {
                        SendCommand(CommandType.Attack, selectedObject, terrainPosition);
                        panel.ControlPanelState = ControlPanelState.None;
                    }
                    break;
                case ControlPanelState.None:
                    if (leftButtonJustPressed && clickInRange)
                    {
                        if (selectedObject != null)
                        {
                            SelectableObject[] objects = this.GameObject.GameScene.GetComponentsInChildren<SelectableObject>();
                            for (int i = 0; i < objects.Length; i++)
                                objects[i].IsSelected = false;
                            selectedObject.IsSelected = true;
                            this.GameObject.GameScene.GetComponentInChildren<UIRoot>().ChoosenObject = selectedObject.GameObject;
                        }
                        else
                            this.GameObject.GameScene.GetComponentInChildren<UIRoot>().ChoosenObject = null;
                    }
                    if (rightButtonJustPressed && clickInRange)
                    {
                        SendCommand(CommandType.None, selectedObject, terrainPosition);
                    }
                    break;
            }
        }

        private void DrawCursorTip(Command c)
        {
            switch (c.CommandType)
            {
                case CommandType.Move:
                    if (c.TargetObject == null)
                    {
                        for (int i = -1; i <= 1; i++)
                            for (int j = -1; j <= 1; j++)
                            {
                                Vector3 pos = new Vector3(i, 0, j) * 64.0f + c.TargetPlace;
                                pos = this.GameObject.GameScene.GetComponentInChildren<Landscape>().WarCraftMapPath.GetHeight(pos);
                                DebugPrimitiveRenderer.AddImmdiatePrimitive(c.TargetPlace, Color.Green);
                            }
                    }
                    break;
                case CommandType.Attack:
                    if (c.TargetObject == null)
                    {
                        for (int i = -1; i <= 1; i++)
                            for (int j = -1; j <= 1; j++)
                            {
                                Vector3 pos = new Vector3(i, 0, j) * 64.0f + c.TargetPlace;
                                pos = this.GameObject.GameScene.GetComponentInChildren<Landscape>().WarCraftMapPath.GetHeight(pos);
                                DebugPrimitiveRenderer.AddImmdiatePrimitive(c.TargetPlace, Color.Red);
                            }
                    }
                    break;
            }
        }

        private void SendCommand(CommandType type, SelectableObject selectedObject, Vector3 terrainPosition)
        {
            Command c;
            if (this.GameObject.GameScene.GetComponentInChildren<TeamManager>().HeroSelected)
            {
                var hero = this.GameObject.GameScene.GetComponentInChildren<TeamManager>().Hero;
                if (selectedObject == null)
                {
                    if (type != CommandType.None)
                        c = new Command(type, terrainPosition);
                    else
                        c = new Command(CommandType.Move, terrainPosition);

                    DrawCursorTip(c);
                    hero.GetComponent<CommandReceiver>().ReceiveCommand(c);
                }
                else
                {
                    var target = selectedObject.GameObject.GetComponent<UnitObject>();
                    if (target == null)
                        throw new Exception("Error");
                    if (target.IsAttackable && target.GameObject.GetParent<Team>().TeamRelationType != TeamRelationType.Self)
                    {
                        SelectableObject[] objects = this.GameObject.GameScene.GetComponentsInChildren<SelectableObject>();
                        for (int i = 0; i < objects.Length; i++)
                            objects[i].IsSelected = false;
                        this.GameObject.GameScene.GetComponentInChildren<TeamManager>().Hero.GetComponent<SelectableObject>().IsSelected = true;
                        selectedObject.IsSelected = true;
                        if (type != CommandType.None)
                            c = new Command(type, target);
                        else
                            c = new Command(CommandType.Attack, target);
                        DrawCursorTip(c);
                        hero.GetComponent<CommandReceiver>().ReceiveCommand(c);
                    }
                    else
                    {
                        selectedObject.IsSelected = true;
                        c = new Command(CommandType.Move, target);
                        DrawCursorTip(c);
                        hero.GetComponent<CommandReceiver>().ReceiveCommand(c);
                    }
                }
            }
        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            AudioManager.Instance.Update(this);


            if (this.GameObject.GameScene.Mode == SceneMode.NormalMode)
            {
                InputManager input = this.GameObject.GameScene.Game.InputManager;
                float seconds = (float)gameTime.ElapsedGameTime.TotalSeconds;

                this.UpdateClicks();
                //this.UpdateMoveTarget();

                if (input.MouseInRectangle() == true)
                {
                    int value = input.MouseDelta;
                    Distance += value * this.ZoomSpeed * seconds;
                }

                if (input.IsKeyDown(Keys.PageDown))
                    this.ReadXml(this._XmlNode);

                if (input.IsKeyJustDown(Keys.Escape))
                    this.GameObject.GameScene.Game.Exit();

                // Rotation
                if (input.IsKeyDown(Keys.Left))
                {
                    this._Rotation.X -= seconds * this.RotateSpeed;
                }
                if (input.IsKeyDown(Keys.Right))
                {
                    this._Rotation.X += seconds * this.RotateSpeed;
                }
                if (input.IsKeyDown(Keys.Up))
                {
                    this._Rotation.Z -= seconds * this.RotateSpeed;
                }
                if (input.IsKeyDown(Keys.Down))
                {
                    this._Rotation.Z += seconds * this.RotateSpeed;
                }

                // Translation
                Vector3 offset = Vector3.Zero;
                Matrix rotation = Matrix.CreateFromYawPitchRoll(this.Rotation.X, this.Rotation.Y, this.Rotation.Z);
                Vector3 _Forward = Vector3.Transform(new Vector3(1, 0, 0), rotation);
                Vector3 _Right = Vector3.Transform(new Vector3(0, 0, 1), rotation);
                _Forward.Y = _Right.Y = 0;

                if (input.OnLeftMargin(this.Margin) && input.MouseInRectangle())
                {
                    offset -= _Right * seconds * _MoveSpeed;
                }
                if (input.OnRightMargin(this.Margin) && input.MouseInRectangle())
                {
                    offset += _Right * seconds * _MoveSpeed;
                }
                if (input.OnTopMargin(this.Margin) && input.MouseInRectangle())
                {
                    offset += _Forward * seconds * _MoveSpeed;
                }
                if (input.OnBottomMargin(this.Margin) && input.MouseInRectangle())
                {
                    offset -= _Forward * seconds * _MoveSpeed;
                }
                this.Translate(offset);
            }

            CalculatePosition();
        }
    }
}
